Weaving maps of multivariate data

Preliminaries

We’ve been working in R for the ‘one-stop-shop’ mix of coding, handling spatial data, and visualization

It’s work in progress, so these aren’t slides, more an annotated RMarkdown notebook

This section gives a sense of the requirements

Source code

Our code lives in these files

source("weaving-space-utils.R")
source("biaxial-weave-units.R")
source("triaxial-weave-units.R")
source("weave-map.R")

Libraries

And here, for reference are the packages we’ve used (so far!)

library(sf)        # vector spatial data
library(wk)        # WKT features and affine transforms
library(tmap)      # thematic maps
library(dplyr)     # data wrangling
library(pracma)    # more matrix stuff

And some more used to make example maps of other approaches

library(ggplot2)   # plotting
library(tricolore) # trivariate choropleths
library(ggtern)    # trivariate legend

Some example data to mess with

region <- st_read("data/vax-auckland-20211006.gpkg")
## Reading layer `vax-auckland-20211006' from data source 
##   `/home/osullid3/Documents/code/weaving-space/data/vax-auckland-20211006.gpkg' 
##   using driver `GPKG'
## Simple feature collection with 155 features and 23 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: 1748016 ymin: 5907966 xmax: 1768438 ymax: 5922015
## Projected CRS: NZGD2000 / New Zealand Transverse Mercator 2000

Note that for our method we need this in part so we can match its CRS

Motivation

Increasingly, we deal with highly multivariate data.

Many approaches can be used to visualise these in spatial contexts, but it’s always challenging. Often we resort to small multiple displays, where each attribute is presented as an individual (small) map. Or perhaps more often, we finding ourselves flipping back and forward among many layers in a GIS or other tool.

Wouldn’t it be nice to see multiple attributes together?! Perhaps to be able to identify patterns across more than one attribute in combination.

We’re not the first to think this, so there are plenty of approaches already around…

Small multiples

This is sf’s default plot output for a dataset

plot(region)

Bivariate choropleths

A crude approach uses alpha values for each attribute, e.g., in tmap

tm_shape(region) + 
  tm_polygons(col = "dose2_uptake", palette = "-Reds", alpha = 0.65, 
              title = "Dose 2 per thousand ") + 
  tm_shape(region) + 
  tm_polygons(col = "pAsian", palette = "Blues", alpha = 0.35,
              title = "% Asian") +
  tm_layout(legend.outside = TRUE)

Or, use something like Jan Caha’s QGIS plugin which implements the approach described by Joshua Stevens in this post

bivariate choropleth

Trivariate choropleths

Mixing three colours is hard, but e.g., the tricolore package can do this…

eth_mix <- tricolore::Tricolore(
  region, p1 = "pEuropean", p2 = "pMaori", p3 = "pAsian", breaks = 5
)
region$eth_mix_tri <- eth_mix$rgb

ggplot(region) + 
  geom_sf(aes(fill = eth_mix_tri)) + 
  scale_fill_identity() +
  annotation_custom(
    grob = ggplotGrob(eth_mix$key + labs(L = 'Pākehā', T = 'Māori', R = 'Asian')),
    xmin = 1.7465e6, xmax = 1.7535e6, ymin = 5.9075e6, ymax = 5.9125e6)

Symbols over choropleths

A simple example

choropleth with symbols

Multivariate symbols

The classic example here is Dorling’s Chernoff faces map of the UK 1987 election

Figure 8.10 Dorling D. 2012. The visualisation of spatial social structure. Chichester, England: John Wiley & Sons.

Multi-element patterns

There are many variations on this idea, but perhaps the most common is a categorical dot map

This example made using tmap and data preparation code from James Smythe’s cultureofinsight blog

dot map of ethnic composition

Our idea

A weave pattern where each ‘thread’ or ‘ribbon’ (the warp and weft) represents a different attribute that can be independently symbolised

from Chaves LF, MD Friberg, LA Hurtado, R Marín Rodríguez, D O’Sullivan, and LR Bergmann. 2021 (online first). Trade, uneven development and people in motion: Used territories and the initial spread of COVID-19 in Mesoamerica and the Caribbean. Socio-Economic Planning Sciences.

This was done with SVG symbol fills in QGIS, and was (very!) fiddly to produce

But it got us thinking about the wider possibilities…

A flax kete

Preliminary implementation

  • Regular rectangular or hex grids of points generated by geospatial tools

tiling a weave unit

  • What repeatable units can tile across such grids to give the appearance of a woven pattern?

We have proof-of-concept R tools to make weave patterns:

Weave units

Biaxial weaves

Plain weaves

Traditional weave patterns with threads in two directions, the warp and the weft. These are all generated drawing on the matrix multiplication underpinnings of weaving!

The simplest case is a plain weave.

rect11_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, type = "plain",
                    ids = "a|b", crs = st_crs(region))
rect11_unit$primitive %>% plot(lwd = 0.01)

This could be useful if clearly distinct palettes were used in the warp and weft elements. More useful is if we change the aspect to the warp and weft elements so we can distinguish directions.

rect11_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
                    ids = "ab|cd", crs = st_crs(region))
rect11_unit$primitive %>% plot(lwd = 0.01)

More threads

This is highly customisable

rect32_unit <-
  get_biaxial_weave_unit(spacing = 150, aspect = sqrt(0.5), 
                    ids = "abc|de", crs = st_crs(region))
rect32_unit$primitive %>% plot(lwd = 0.01)

Missing threads

We can even leave gaps or duplicate threads

rect34_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
                    ids = "ab-|cc-d", crs = st_crs(region))
rect34_unit$primitive %>% plot(lwd = 0.01)

Twill weaves and basket weaves

There are near infinite possibilities here…

twill_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "twill", n = 2, 
                         aspect = 0.6, ids = "ab|cd", crs = st_crs(region))
twill_unit$primitive %>% plot(lwd = 0.01)

basket_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "basket", n = 2, 
                         aspect = 0.8, ids = "ab|cd", crs = st_crs(region))
basket_unit$primitive %>% plot(lwd = 0.01)

Other weaves

And here is another example (this makes use of the capability to generate any weavable repeat pattern, but the pattern is hardcoded for now…)

this_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "this", aspect = 0.8,
                        ids = "a|b", crs = st_crs(region))
this_unit$primitive %>% plot(lwd = 0.01)

Triaxial weaves

Hexagonal

We can also make weaves with threads running in 3 directions. This example is based on a hexagonal tileable unit, and can allow for more than one thread in each direction

hex_unit <- ## hex example
  get_triaxial_weave_unit(spacing = 600, margin = 2,
                          ids = "a|b|cd", type = "hex", crs = st_crs(region))
hex_unit$primitive %>% plot(lwd = 0.01)

A cube is another option, although this produces some odd 3D effects when tiled

cube_unit <-
  get_triaxial_weave_unit(spacing = 600, ids = "ab|cd|ef", margin = 5, 
                          type = "cube", crs = st_crs(region))
## Warning: attribute variables are assumed to be spatially constant throughout all
## geometries
cube_unit$primitive %>% plot(lwd = 0.01)

Diamond

An alternative way to produce a triangular weave is with a diamond repeating unit with angles 60° and 120°

diamond_unit <-   ## diamond example
  get_triaxial_weave_unit(spacing = 600, margin = 10,
                          ids = "a|b|c", type = "diamond", crs = st_crs(region))
diamond_unit$primitive %>% plot(lwd = 0.01)

Tiling diamond units and transforming weaves

Tiling the diamond unit is slightly more involved than rectangular or hexagonal units:

  • transform to a rectangle (applying the same transformation to the map)
  • perform the tiling (rectangularly)
  • invert the transformation

We allow for any of the weave unit ‘tiles’ to be transformed in this way.

For example we can transform the region to tile like this

region[, 2] %>% sf_diamond_to_square() %>% tm_shape() + tm_polygons()

then tile it with any weave unit, and transform back to the original map coordinates. This allows us to (say) take the twill tile above and apply it in this form:

twill_unit$primitive %>% sf_square_to_diamond() %>% plot(lwd = 0.01)

Weave a map

Now ‘weave’ the map with one of the weave unit tiles applied to the region

We can also optionally specify a rotation and an affine transformation

cloth <- weave_layer(twill_unit, region, angle = 30)
# cloth <- weave_layer(weave_unit, region,
#                      transform = affine_abcd(1, 0, 0.5, 1))

Make the map

Do this in tmap “view” mode for a zoomable web map.

tmap_mode("view")

Split the data by the id so that it is convenient to symbolise them separately

layers <- cloth %>% split(as.factor(cloth$id))
  • Each id value can be symbolised separately using symbolisation the data can support
  • We can also plot the region data as a choropleth if desired
tm_shape(region, name = "Dose 2 uptake") +
  tm_fill(col = "dose2_uptake", palette = "inferno", style = "cont", title = "Dose 2 per 1000") +
  tm_shape(layers$a, name = "Pākehā") +
  tm_fill(col = "pEuropean", palette = "Greys", title = "% Pākehā", n = 3) +
  tm_shape(layers$b, name = "Māori") +
  tm_fill(col = "pMaori", palette = "Reds", title = "% Māori", n = 3) +
  tm_shape(layers$c, name = "Pasifika") +
  tm_fill(col = "pPacific", palette = "Purples", title = "% Pasifika", n = 3) +
  tm_shape(layers$d, name = "Asian") +
  tm_fill(col = "pAsian", palette = "Greens", title = "% Asian", n = 3) +
  tm_basemap(server = "CartoDB.VoyagerNoLabels")

Online full screen version

Another example

Write the weave layers

We can also save out to a multi-layer GPKG for use in any tool

write_weave_layers(cloth, region, "data/cloth.gpkg")

Further work

There is lots to do (at least potentially!)

  • Clarify the API for the tools to make their usage clearer
  • Figure out how to make legends…
  • Develop guidelines for what works
  • How does colour work in this setting (how many, what combinations?)
  • Explore what symbolisations work (continuous, classified, categorical?)
  • Understand better how orientation operates

Acknowledgments

Questions?